﻿using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

namespace SearchCss
{
    public partial class SearchCssForm : Form
    {

        // c# 里面这种类里面的不变量怎么定义好?
        private Regex importCSSRegex = new Regex(@"@import\s+url\((.*?)\)");
        private Dictionary<string, List<string>> revertIndex;
        private HtmlAgilityPack.HtmlDocument htmlDoc;

        // 是否已经建立索引
        private bool IndexBuiled = false;     

        public SearchCssForm()
        {
            InitializeComponent();
            htmlDoc = new HtmlAgilityPack.HtmlDocument();
            htmlDoc.OptionFixNestedTags = true;
        }

        private void SelectCssFile(object sender, EventArgs e)
        {
            var fileDialog = new OpenFileDialog();

            fileDialog.Title = "请选择css文件";
            fileDialog.Filter = "css文件(*.css)|*.css";
            if (fileDialog.ShowDialog() == DialogResult.OK)
            {
                cssFileTextbox.Text = fileDialog.FileName;
            }
        }

        private void SelectDir(object sender, EventArgs e)
        {
            var dialog = new FolderBrowserDialog();
            dialog.Description = "请选择文件路径";
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                DirTextbox.Text = dialog.SelectedPath;
            }
        }

        private void Search(object sender, EventArgs e)
        {
            clearResultBox();

            if (cssFileTextbox.Text == "")
            {
                MessageBox.Show("请选择css文件");
                return;
            }

            if (DirTextbox.Text == "")
            {
                MessageBox.Show("请选择项目文件夹");
                return;
            }

            if(!IndexBuiled)
            {
                MessageBox.Show("请先建立索引");
                return;
            }

            if (!revertIndex.ContainsKey(cssFileTextbox.Text))
            {
                MessageBox.Show("css文件没有被引用");
                return;
            }


            var result = GetCssReference(cssFileTextbox.Text);

            result = result.Distinct().ToList();

            foreach (string f in result)
            {
                print(f);
            }
        }

        // 获取引用某个CSS文件的HTML文件，如果被另外一个CSS文件引用，要查找该CSS文件被哪个html文件引用
        private List<string> GetCssReference(string filename)
        {

            List<string> result = new List<string>();

            if (!revertIndex.ContainsKey(filename))
            {
                return result;
            }

            List<string> cssFiles = new List<string>();

            foreach (string f in revertIndex[filename])
            {
                var extension = Path.GetExtension(f);
                if (extension == ".css")
                {
                    cssFiles.Add(f);
                }
                else
                {
                    result.Add(f);
                }
            }

            if (cssFiles.Count() > 0)
            {
                foreach (string css in cssFiles)
                {
                    result.AddRange(GetCssReference(css));
                }
            }

            return result;
        }

        // 从link标签获取css路径
        private string GetCssPathFromLinkTag(HtmlNode link)
        {
            if(link.Attributes["rel"].Value != "stylesheet")
            {
                return "";
            }

            var href = link.Attributes["href"].Value;

            if (IsAbsolutePath(href))
            {
                return "";
            }

            return stripCssPath(href);
        }

        // 从css文件的一行中获取css路径
        private string GetCssPathFromCssFileLine(string line)
        {
            string href ="";
            
            if (!line.Contains("@import"))                
               return "";

                    
            var match = importCSSRegex.Match(line);
                   
            if (!match.Success)
                return "";
            
            href = match.Groups[1].Value;
                        
            if (IsAbsolutePath(href))
                return "";
     
            return stripCssPath(href);   
        }

        // 计算一个HTML文件引用了哪些css
        private List<string> fetchCssPathFromHtml(string filename)
        {
            var l = new List<string>();
            string href = "";
            htmlDoc.Load(filename);
            var links = htmlDoc.DocumentNode.Descendants("link");

            foreach (var link in links)
            {
                href = GetCssPathFromLinkTag(link);

                if (href != "")
                {
                    l.Add(CalCssFilePath(filename, href));
                }
            }       
            return l;
        }

        // 计算一个CSS文件引用了哪些css
        private List<string> fetchCssPathFromCSS(string filename)
        {
            var l = new List<string>();
            string href = "";

            foreach (string line in File.ReadLines(filename))
            {
                href = GetCssPathFromCssFileLine(line);

                if (href != "")
                {
                    l.Add(CalCssFilePath(filename, href));
                }
            }

            return l;
        }

        // 清理CSS文件路径
        private string stripCssPath(string csspath) {
            // 排除引号
            csspath = csspath.Trim(new char[] { '"', '\'' });

            // 去掉url后的参数
            var quotePos = csspath.IndexOf("?");
            if (quotePos != -1)
            {
                csspath = csspath.Substring(0, quotePos);
            }

            return csspath;
        }

        // 判断路径是否绝对路径，命名要斟酌一下
        private bool IsAbsolutePath(String href)
        {
            if (href.StartsWith("https:") || href.StartsWith("http:") || href.StartsWith(@"/") || href.StartsWith(@"//"))
                return true;
            else
                return false;
        }

        private string CalCssFilePath(string h, string href)
        {
            try {
                href = href.Replace('/', '\\');
                return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(h), href));
            }
               catch (Exception ex)
            {
                MessageBox.Show("css 代码路径 : '" + href + "'");
                return "";
            }
        }

        private void clearResultBox()
        {
            // 清空textbox
            resultBox.Text = "";
        }

        private void print(string s)
        {
            resultBox.Text += s + Environment.NewLine;

        }

        private void buildIndex(object sender, EventArgs e)
        {
            if (DirTextbox.Text == "")
            {
                MessageBox.Show("请选择项目文件夹");
                return;
            }
         
            Dictionary<string, List<string>> tree = new Dictionary<string, List<string>>();
            revertIndex = new Dictionary<string, List<string>>();
            List<string> cssList = new List<string>();

            var files = Directory.GetFiles(DirTextbox.Text, "*.*", SearchOption.AllDirectories);

            // build tree
            foreach (string filename in files)
            {
                var extension = Path.GetExtension(filename);
                

                if (extension == ".css")
                {
                    cssList = fetchCssPathFromCSS(filename);
                }

                if (extension == ".html")
                {
                    cssList = fetchCssPathFromHtml(filename);
                }

                if (cssList.Count() > 0)
                {
                    tree.Add(filename, cssList);
                }

            }

            // build  revertedIndex
            foreach (KeyValuePair<string, List<string>> kvp in tree)
            {
                foreach (string s in kvp.Value)
                {
                    if (revertIndex.ContainsKey(s))
                    {
                        revertIndex[s].Add(kvp.Key);
                    }
                    else
                    {
                        var l = new List<string>();
                        l.Add(kvp.Key);
                        revertIndex.Add(s, l);
                    }

                }
            }

            IndexBuiled = true;

            // 保存索引
            FileStream fs = new FileStream("CssIndex.dat", FileMode.Create);

            BinaryFormatter formatter = new BinaryFormatter();

            try
            {
                formatter.Serialize(fs, revertIndex);
            }
            catch (SerializationException error)
            {
                MessageBox.Show("Failed to serialize. Reason: " + error.Message);
                throw;
            }
            finally
            {
                fs.Close();
            }

            fs = new FileStream("CssIndex.dat", FileMode.Open);
            try 
            {

                var revertIndex_from_file = (Dictionary<string, List<string>>)formatter.Deserialize(fs);
                OutputIndex(revertIndex_from_file);
            }
            catch (SerializationException error)
            {
                Console.WriteLine("Failed to deserialize. Reason: " + error.Message);
                throw;
            }
            finally
            {
                fs.Close();
            }

            
            MessageBox.Show("索引建立完成。如果文件引用关系发生改变，请重新建立索引。");
        }

        private void OutputIndex(Dictionary<string, List<string>> revertIndex)
        {
            clearResultBox();
            foreach (KeyValuePair<string, List<string>> kvp in revertIndex)
            {
                print(kvp.Key + " 被以下文件引用:");
                foreach (string s in kvp.Value)
                {
                    print(s);
                }
                print(Environment.NewLine);
            }
        }
    }
}
